package me.aartikov.alligator.navigationfactories;

import me.aartikov.alligator.converters.*;
import me.aartikov.alligator.destinations.AbilityDestination;
import me.aartikov.alligator.destinations.Destination;
import me.aartikov.alligator.destinations.DialogFragmentDestination;
import me.aartikov.alligator.destinations.FragmentDestination;
import me.aartikov.alligator.helpers.MiFraction;
import me.aartikov.alligator.Screen;
import me.aartikov.alligator.ScreenResult;
import me.aartikov.alligator.helpers.DialogFraction;
import me.aartikov.alligator.helpers.ScreenClassHelper;
import ohos.aafwk.ability.Ability;

import java.util.LinkedHashMap;
import java.util.Map;


/**
 * Navigation factory with screen registration methods.
 */

public class RegistryNavigationFactory implements NavigationFactory {
    private Map<Class<? extends Screen>, Destination> mDestinations = new LinkedHashMap<>();
    private ScreenClassHelper mScreenClassHelper = new ScreenClassHelper();
    private int mRequestCode = 1000;

    @Override

    public Destination getDestination(Class<? extends Screen> screenClass) {
        return mDestinations.get(screenClass);
    }

    @Override

    public Class<? extends Screen> getScreenClass(Ability activity) {
        return mScreenClassHelper.getScreenClass(activity);
    }

    @Override
    public Class<? extends Screen> getScreenClass(MiFraction fragment) {
        return mScreenClassHelper.getScreenClass(fragment);
    }

    @Override

    public Class<? extends Screen> getScreenClass(int requestCode) {
        return mScreenClassHelper.getScreenClass(requestCode);
    }

    @Override

    public Class<? extends Screen> getPreviousScreenClass(Ability activity) {
        return mScreenClassHelper.getPreviousScreenClass(activity);
    }

    /**
     * Registers a screen represented by an activity using a custom {@link IntentConverter}.
     *
     * @param screenClass   screen class
     * @param activityClass activity class
     * @param converter     intent converter
     * @param <ScreenT>     screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerBaseAbility(Class<ScreenT> screenClass,
                                                         Class<? extends Ability> activityClass,
                                                         IntentConverter<ScreenT> converter) {
        AbilityDestination destination = new AbilityDestination(screenClass, activityClass, converter, mScreenClassHelper);
        registerDestination(screenClass, destination);
        mScreenClassHelper.addActivityClass(activityClass, screenClass);
    }

    /**
     * Registers a screen represented by an activity using {@link DefaultIntentConverter}.
     *
     * @param screenClass   screen class
     * @param activityClass activity class
     * @param <ScreenT>     screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerBaseAbility(Class<ScreenT> screenClass, Class<? extends Ability> activityClass) {
        IntentConverter<ScreenT> converter = new DefaultIntentConverter<>(screenClass, activityClass);
        registerBaseAbility(screenClass, activityClass, converter);
    }

    /**
     * Registers a screen represented by an activity using {@link OneWayIntentConverter}. Should be used for external activities only.
     *
     * @param screenClass screen class
     * @param converter   one-way intent converter
     * @param <ScreenT>   screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerBaseAbility(Class<ScreenT> screenClass, OneWayIntentConverter<ScreenT> converter) {
        AbilityDestination destination = new AbilityDestination(screenClass, null, converter, mScreenClassHelper);
        registerDestination(screenClass, destination);
    }

    /**
     * Registers a screen represented by an activity for result using a custom {@link IntentConverter} and a custom {@link ScreenResultConverter}.
     *
     * @param screenClass           screen class
     * @param activityClass         activity class
     * @param screenResultClass     screen result class
     * @param converter             intent converter
     * @param screenResultConverter screen result converter
     * @param <ScreenT>             screen type
     * @param <ScreenResultT>       screen result type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen, ScreenResultT extends ScreenResult> void registerBaseAbilityForResult(Class<ScreenT> screenClass,
                                                                                                      Class<? extends Ability> activityClass,
                                                                                                      Class<ScreenResultT> screenResultClass,
                                                                                                      IntentConverter<ScreenT> converter,
                                                                                                      ScreenResultConverter<ScreenResultT> screenResultConverter) {

        AbilityDestination destination = new AbilityDestination(screenClass, activityClass, converter, screenResultClass, screenResultConverter, mRequestCode, mScreenClassHelper);
        registerDestination(screenClass, destination);
        mScreenClassHelper.addActivityClass(activityClass, screenClass);
        mScreenClassHelper.addRequestCode(mRequestCode, screenClass);
        mRequestCode++;
    }

    /**
     * Registers a screen represented by an activity for result using {@link DefaultIntentConverter} and the default {@link ScreenResultConverter}.
     *
     * @param screenClass       screen class
     * @param activityClass     activity class
     * @param screenResultClass screen result class. {@code ScreenT} must be {@code Serializable} or {@code Parcelable}.
     * @param <ScreenT>         screen type
     * @param <ScreenResultT>   screen result type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen, ScreenResultT extends ScreenResult> void registerBaseAbilityForResult(Class<ScreenT> screenClass,
                                                                                                      Class<? extends Ability> activityClass,
                                                                                                      Class<ScreenResultT> screenResultClass) {

        IntentConverter<ScreenT> converter = new DefaultIntentConverter<>(screenClass, activityClass);
        ScreenResultConverter<ScreenResultT> screenResultConverter = new DefaultScreenResultConverter<>(screenResultClass);
        registerBaseAbilityForResult(screenClass, activityClass, screenResultClass, converter, screenResultConverter);
    }

    /**
     * Registers a screen represented by an activity for result using {@link OneWayIntentConverter} and {@link OneWayScreenResultConverter}. Should be used for external activities only.
     *
     * @param screenClass           screen class
     * @param screenResultClass     screen result class
     * @param converter             one-way intent converter
     * @param screenResultConverter one-way screen result converter
     * @param <ScreenT>             screen type
     * @param <ScreenResultT>       screen result type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen, ScreenResultT extends ScreenResult> void registerAbilityForResult(Class<ScreenT> screenClass,
                                                                                                      Class<ScreenResultT> screenResultClass,
                                                                                                      OneWayIntentConverter<ScreenT> converter,
                                                                                                      OneWayScreenResultConverter<ScreenResultT> screenResultConverter) {

        AbilityDestination destination = new AbilityDestination(screenClass, null, converter, screenResultClass, screenResultConverter, mRequestCode, mScreenClassHelper);
        registerDestination(screenClass, destination);
        mScreenClassHelper.addRequestCode(mRequestCode, screenClass);
        mRequestCode++;
    }

    /**
     * Registers a screen represented by a fragment using a custom {@link FragmentConverter}.
     *
     * @param screenClass screen class
     * @param converter   fragment converter
     * @param <ScreenT>   screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerFragment(Class<ScreenT> screenClass, FragmentConverter<ScreenT> converter) {
        FragmentDestination destination = new FragmentDestination(screenClass, converter, null, mScreenClassHelper);
        registerDestination(screenClass, destination);
    }

    /**
     * Registers a screen represented by a fragment using {@link DefaultFragmentConverter}.
     *
     * @param screenClass   screen class
     * @param fragmentClass fragment class
     * @param <ScreenT>     screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerFraction(Class<ScreenT> screenClass, Class<? extends MiFraction> fragmentClass) {
        FragmentConverter<ScreenT> converter = new DefaultFragmentConverter<>(screenClass, fragmentClass);
        registerFragment(screenClass, converter);
    }

    /**
     * Registers a screen represented by a fragment for result using a custom {@link FragmentConverter}.
     *
     * @param screenClass       screen class
     * @param converter         fragment converter
     * @param screenResultClass screen result class
     * @param <ScreenT>         screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerFractionForResult(Class<ScreenT> screenClass,
                                                                   FragmentConverter<ScreenT> converter,
                                                                   Class<? extends ScreenResult> screenResultClass) {
        FragmentDestination destination = new FragmentDestination(screenClass, converter, screenResultClass, mScreenClassHelper);
        registerDestination(screenClass, destination);
    }

    /**
     * Registers a screen represented by a fragment for result using {@link DefaultFragmentConverter}.
     *
     * @param screenClass       screen class
     * @param fragmentClass     fragment class
     * @param screenResultClass screen result class
     * @param <ScreenT>         screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerFractionForResult(Class<ScreenT> screenClass,
                                                                   Class<? extends MiFraction> fragmentClass,
                                                                   Class<? extends ScreenResult> screenResultClass) {
        FragmentConverter<ScreenT> converter = new DefaultFragmentConverter<>(screenClass, fragmentClass);
        registerFractionForResult(screenClass, converter, screenResultClass);
    }

    /**
     * Registers a screen represented by a dialog fragment using a custom {@link FragmentConverter}.
     *
     * @param screenClass screen class
     * @param converter   dialog fragment converter
     * @param <ScreenT>   screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerDialogFragment(Class<ScreenT> screenClass, DialogFragmentConverter<ScreenT> converter) {
        DialogFragmentDestination destination = new DialogFragmentDestination(screenClass, converter, null, mScreenClassHelper);
        registerDestination(screenClass, destination);
    }

    /**
     * Registers a screen represented by a fragment using {@link DefaultDialogFragmentConverter}.
     *
     * @param screenClass         screen class
     * @param dialogFragmentClass dialog fragment class
     * @param <ScreenT>           screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerDialogFragment(Class<ScreenT> screenClass, Class<? extends DialogFraction> dialogFragmentClass) {
        DialogFragmentConverter<ScreenT> converter = new DefaultDialogFragmentConverter<>(screenClass, dialogFragmentClass);
        registerDialogFragment(screenClass, converter);
    }

    /**
     * Registers a screen represented by a dialog fragment for result using a custom {@link DialogFragmentConverter}.
     *
     * @param screenClass       screen class
     * @param converter         dialog fragment converter
     * @param screenResultClass screen result class
     * @param <ScreenT>         screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerDialogFragmentForResult(Class<ScreenT> screenClass,
                                                                         DialogFragmentConverter<ScreenT> converter,
                                                                         Class<? extends ScreenResult> screenResultClass) {
        DialogFragmentDestination destination = new DialogFragmentDestination(screenClass, converter, screenResultClass, mScreenClassHelper);
        registerDestination(screenClass, destination);
    }

    /**
     * Registers a screen represented by a dialog fragment for result using {@link DefaultDialogFragmentConverter}.
     *
     * @param screenClass         screen class
     * @param dialogFragmentClass fragment class
     * @param screenResultClass   screen result class
     * @param <ScreenT>           screen type
     * @throws IllegalArgumentException if the screen is already registered
     */
    public <ScreenT extends Screen> void registerDialogFragmentForResult(Class<ScreenT> screenClass,
                                                                         Class<? extends DialogFraction> dialogFragmentClass,
                                                                         Class<? extends ScreenResult> screenResultClass) {
        DialogFragmentConverter<ScreenT> converter = new DefaultDialogFragmentConverter<ScreenT>(screenClass, dialogFragmentClass);
        registerDialogFragmentForResult(screenClass, converter, screenResultClass);
    }


    protected void registerDestination(Class<? extends Screen> screenClass, Destination destination) {
        if (getDestination(screenClass) != null) {
            throw new IllegalArgumentException("Screen " + screenClass.getSimpleName() + " is already registered.");
        }
        mDestinations.put(screenClass, destination);
    }
}
